﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using StressTesterCommon;
using System.Xml;
using System.Xml.Linq;


namespace StressClient
{
    public class TestThread
    {
        public Connection Con;
        public Thread MyThread;
    }

    public class StressClient:IDisposable
    {
        #region Event declarations
        public delegate void TestTargetConnection(bool Result);
        public event TestTargetConnection OnTestConnection;

        public delegate void ServerMessageRecieved(TCPMessage Msg);
        public event ServerMessageRecieved MessageRecieved;

        public delegate void ClientConfigured();
        public event ClientConfigured Configured;

        public delegate void QCompleted(QueryResult Result);
        public event QCompleted OnQueryCompleted;

        public delegate void TestCompleted();
        public event TestCompleted OnTestCompleted;

        public delegate void TestAborted();
        public event TestAborted OnTestAborted;

        public delegate void TestStarted();
        public event TestStarted OnTestStarted;

        public delegate void Disconnected();
        public event Disconnected OnDisconnect;
        #endregion
        #region Members
        private int _ThreadsCompletedTest = 0;
        private int _ServerPort;

        public int ServerPort
        {
            get { return _ServerPort; }
            set { _ServerPort = value; }
        }

        private string _ServerAddress;

        public string ServerAddress
        {
            get { return _ServerAddress; }
            set { _ServerAddress = value; }
        }
        
        private TcpClient _Client;
        private Thread _ListenThread;
        private bool _Disconnect = false;
        private Test _TestConfiguration;
        private bool _AbortTest = false;

        private List<Thread> _TestThreads; 
        private DateTime _LastHeartBeatSent;

        private int _HeartBeatFrequency = 5;

        public int HeartBeatFrequency
        {
            get { return _HeartBeatFrequency; }
            set { _HeartBeatFrequency = value; }
        }
        #endregion
        #region Constructors and overloads
        public Test TestConfiguration
        {
            get { return _TestConfiguration; }
            set { _TestConfiguration = value; }
        }

        public void Dispose()
        {
            Dispose(true);
        }
        protected virtual void Dispose(bool dispose)
        {
            if (_ListenThread != null)
            {
                if (_ListenThread.IsAlive)
                {
                    _ListenThread.Abort();
                }
                _ListenThread = null;
            }
            if (_Client != null)
            {
                if (_Client.Connected)
                {
                    _Client.Close();
                }
                _Client = null;
            }
        }
        #endregion
        #region Methods
        private void SendMsg(MessageTypes Type, string Message)
        {
            if (_Client != null && _Client.Connected)
            {
                try
                {
                    byte[] Msg = TCPMessage.FormatMessage(Type, Message);
                    NetworkStream s = _Client.GetStream();
                    s.Write(Msg, 0, Msg.Length);
                    s.Flush();
                }
                catch (Exception)
                {
                    this.Disconnect(false);
                }
            }
        }
        public bool Disconnect(bool ServerForced = false)
        {
            bool Ret = true;
            _Disconnect = true;
            if (!ServerForced)
            {
                try
                {
                    SendMsg(MessageTypes.ClientDisconnect, "");
                }
                catch { }
            }
            if (OnDisconnect != null)
            {
                OnDisconnect();
            }

            _Disconnect = true;
            if (_Client != null) 
            {
                if(_Client.Connected) {
                    _Client.Close();
                }
                _Client = null;
            }

            if (_ListenThread != null)
            {
                if (_ListenThread.IsAlive)
                {
                    _ListenThread.Abort();
                }
                _ListenThread = null;
            }
            
            return Ret;
        }
        public bool Connect(string Address, int Port)
        {
            _Disconnect = false;
            if (_Client != null && _Client.Connected)
            {
                return false;
            }

            _Client = new TcpClient();
            _ServerPort = Port;

            try
            {
                _Client.Connect(Address, _ServerPort);
            }
            catch (SocketException s2)
            {
                return false;
            }
            _LastHeartBeatSent = DateTime.Now;
            Listen();
            return true;
        }
        public void Listen()
        {
            _ListenThread = new Thread(new ThreadStart(Listen2));
            _ListenThread.Start();
        }
        public void Listen2()
        {
            NetworkStream Read = _Client.GetStream();

            while (!_Disconnect)
            {
                try
                {
                    TimeSpan T = DateTime.Now - _LastHeartBeatSent;
                    if (T.Seconds > _HeartBeatFrequency)
                    {
                        SendMsg(MessageTypes.HeartBeat, "");
                    }

                    if (Read.DataAvailable)
                    {
                        StressTesterCommon.TCPMessage Msg = StressTesterCommon.TCPReader.ReadMessageFromStream(Read);
                        MessageRecieved(Msg);
                        HandleMessage(Msg);
                    }
                    Thread.Sleep(50);
                }
                catch (Exception)
                {
                    this.Disconnect();

                }
            }
            if (_Client != null && _Client.Connected)
            {
                _Client.Close();
            }
        }
        private void HandleMessage(TCPMessage Message)
        {
            switch (Message.MessageType)
            {
                case MessageTypes.ClientProvideIdData:
                    SendClientData();
                    break;
                case MessageTypes.TestConfiguration:
                    TestConfiguration = new Test();
                    try
                    {
                        TestConfiguration.LoadFromXDocument(XDocument.Parse(Message.Message));
                        SendMsg(MessageTypes.TestConfigurationOk, "");
                        if (Configured != null)
                        {
                            Configured();
                        }
                    }
                    catch (Exception)
                    {
                        SendMsg(MessageTypes.TestConfigurationFailed, "");
                    }
                    
                    break;
                case MessageTypes.ClientDisconnect:
                    this.Disconnect();
                    break;
                case MessageTypes.TestConnection:
                    TestConnection();
                    break;
                case MessageTypes.ExecuteTest:
                    ExecuteTest();
                    break;
                case MessageTypes.ClientAbortTest:
                    AbortTest();
                    break;
                case MessageTypes.HeartBeat:
                   
                    break;
                default:
                    break;
            }
            
        }
        public void AbortTest()
        {
            if (_TestThreads != null)
            {
                _AbortTest = true;
            }
            else
            {
                OnTestAborted();
            }
        }
        public bool TestConnection()
        {
            bool ret = false;
            if (this.TestConfiguration == null)
            {
                SendMsg(MessageTypes.TestConnectionFailed, "");
                return false;
            }
            
            ret = this.TestConfiguration.Connection.TestConnection();
            if (ret)
            {
                if (OnTestConnection != null)
                {
                    OnTestConnection(true);
                }
                SendMsg(MessageTypes.TestConnectionOk, "");
            }
            else
            {
                if (OnTestConnection != null)
                {
                    OnTestConnection(false);
                }
                SendMsg(MessageTypes.TestConnectionFailed, "");
            }
            return ret;
        }
        public void ExecuteTest()
        {
            if (_TestConfiguration == null)
            {
                return;
            }
            _ThreadsCompletedTest = 0;
            _AbortTest = false;
            _TestThreads = new List<Thread>();
            //Add execute test threads
            for (int k = 0; k < _TestConfiguration.TestThreadCount; k++)
            {
                Thread NewThread  = new Thread(new ThreadStart(ExecuteTest2));
                _TestThreads.Add(NewThread);
                
            }
            if (OnTestStarted != null) OnTestStarted();
            SendMsg(MessageTypes.ClientTestStarted, "");
            foreach (Thread s in _TestThreads)
            {
                s.Start();
            }

        }
        public void ExecuteTest2()
        {
            //Obtain my connection
            SSASConnection C = new SSASConnection();
            C.LoadFromXelement(this.TestConfiguration.Connection.ToXElement());
            C.Connect();
            for (int i = 0; i < this.TestConfiguration.TimesToRepeat; i++)
            {
                if (_AbortTest || _Disconnect)
                {
                    if (OnTestAborted != null)
                    {
                        OnTestAborted();
                    }
                    C.Disconnect();
                    SendMsg(MessageTypes.ClientAbortTest, "");
                    break;
                }
                List<int> ThisRun = new List<int>();

                for (int j = 0; j < _TestConfiguration.Queries.Count; j++)
                {
                    ThisRun.Add(j);
                }

                QueryResult Res;
                Random RndTime = new Random();
                int Delay = 0;
                while (ThisRun.Count > 0)
                {
                    if (_Disconnect || _AbortTest) //Exit test loop if client wants to disconnect
                    {
                        if (OnTestAborted != null)
                        {
                            OnTestAborted();
                        }
                        SendMsg(MessageTypes.ClientAbortTest, "");
                        break;
                    }
                    Res = C.ExecuteQuery(PickQuery(_TestConfiguration.QueryPickMethod, _TestConfiguration.Queries, ThisRun));
                  
                    
                    if (this.TestConfiguration.MSDelayBetweenQueriesFrom == this.TestConfiguration.MSDelayBetweenQueriesTo)
                    {
                        Delay = this.TestConfiguration.MSDelayBetweenQueriesFrom;
                    }
                    else
                    {
                        Delay = RndTime.Next(this.TestConfiguration.MSDelayBetweenQueriesFrom, this.TestConfiguration.MSDelayBetweenQueriesTo);
                    }
                    Thread.Sleep(Delay);
                    if (OnQueryCompleted != null)
                    {
                        OnQueryCompleted(Res);
                    }
                    SendMsg(MessageTypes.QueryResult, Res.ToString());
                }
            }
           // _TestConfiguration.Connection.Disconnect();
            _ThreadsCompletedTest++;
            if (_ThreadsCompletedTest == _TestThreads.Count)
            {
                SendMsg(MessageTypes.TestCompleted, ""); //Server
                if (OnTestCompleted != null)
                {
                    this.OnTestCompleted();//Listeners
                }
            }
        }
        private Query PickQuery(QueryPickMode Mode, List<Query> List, List<int> EliminationList) 
        {
            Query ret;
            if (Mode == QueryPickMode.Sequential)
            {
                ret = List[EliminationList[0]];
                EliminationList.RemoveAt(0);

            }
            else
            {
                Random r = new Random();
                int PickMe = r.Next(0, EliminationList.Count-1);
                ret = List[EliminationList[PickMe]];
                EliminationList.RemoveAt(PickMe);
            }
            return ret;
        }
        private void SendClientData()
        {
            //Get client data
            string _MachineName = System.Environment.MachineName;
            string _DomainName = System.Environment.UserDomainName;
            string _UserName = System.Environment.UserName;
            XElement Tree = new System.Xml.Linq.XElement("clientdata", 
                new XElement("machinename", _MachineName),
                new XElement("domainname", _DomainName),
                new XElement("username", _UserName)             
                );
            NetworkStream Writer = _Client.GetStream();
            byte[] Msg = TCPMessage.FormatMessage(MessageTypes.ClientProvideIdData, Tree.ToString());
            try
            {
                Writer.Write(Msg, 0, Msg.Length);
                Writer.Flush();
            }
            catch
            {
                throw;
            }


        }
        #endregion

    }
}
